package model

import (
    "gitee.com/zwx784533/zwx2018/app"
    "fmt"
    "gitee.com/zwx784533/zwx2018/utils"
    "gitee.com/zwx784533/zwx2018/config"
    "crypto/md5"
    "time"
    "errors"
    "encoding/base64"
    "github.com/garyburd/redigo/redis"
)

type User struct {
    Uid int `json:"uid"`
    Nick string `json:"nick"`
    Avatar string `json:"avatar"`
    LastToken string `json:"-"`
    LastIdSource int `json:"-"`
    Bio string `json:"bio"`
    CreateTime int `json:"create_time"`
    UpdateTime int `json:"update_time"`
}

type UserLogin struct {
    Id string `json:"id"`
    IdSource int `json:"id_source"`
    Uid int `json:"uid"`
    Password string `json:"-"`
    CreateTime int `json:"create_time"`
}

const (
    UserIdSourceEmail  = 0
    UserIdSourcePhone  = 1
    UserIdSourceWechat = 2
    UserIdSourceQQ     = 3
    BaseUid = 100000
)

func LoginByEmail(email string, password string) (user *User, err error)  {
    loginData, err := GetLoginData(email, UserIdSourceEmail)
    if err != nil {
        return nil,err
    }
    if loginData.Uid == 0 {
        return nil, errors.New("用户不存在")
    }
    if CheckPassword(password, loginData.Password) == false {
        return nil,errors.New("邮箱和密码不匹配")
    }
    user, err = loginData.Login()
    if err != nil {
        return nil, err
    }
    return user,nil
}

func GetLoginData(id string, idSource int) (user *UserLogin, err error) {
    hashVal := utils.StrHash(id)
    table := fmt.Sprintf("t_user_login_%02d", hashVal%100)
    // 选择 db_user
    db, err := GetDb("db_user")

    if err != nil {
        go app.Error(err)
        return nil, err
    }
    sql := "select id,id_source,uid,password from " + table + " where id=? and id_source=? limit 1"
    rows, err := db.Query(sql, id, idSource)
    if err != nil {
        go app.Error(err)
        rows.Close()
        return nil, err
    }
    loginData := &UserLogin{}
    for rows.Next() {
        err = rows.Scan(&loginData.Id, &loginData.IdSource, &loginData.Uid, &loginData.Password)
        if err != nil {
            go app.Error("scan error:", err)
            continue
        }
        break
    }
    rows.Close()
    return loginData, nil
}

func CheckPassword(password string, hashStr string) bool {
    str := fmt.Sprintf("%s%s", config.ServerConf.HashSalt, password)
    str = fmt.Sprintf("%x", md5.Sum([]byte(str)))
     return str == hashStr
}

// 进行登录
func (u *UserLogin) Login() (user *User, err error)  {
    user, err = GetUserData(u.Uid)
    if err != nil {
        return nil, err
    }
    // 生成 token
    token := generateToken(user.Uid, user.LastToken)
    if token == "" {
        return nil, errors.New("生成token失败")
    }
    // 更新用户登录信息
    user.LastToken = token
    user.LastIdSource = u.IdSource
    user.UpdateTime = int(time.Now().Unix())
    user.Save()

    // 昵称 base64 解码
    nick, _ := base64.StdEncoding.DecodeString(user.Nick)
    user.Nick = string(nick)
    bio, _ := base64.StdEncoding.DecodeString(user.Bio)
    user.Bio = string(bio)
    return user, nil
}

// 获取用户信息
func GetUserData(uid int) (user *User, err error) {
    table := fmt.Sprintf("t_user_info_%02d", uid%100)
    // 选择 db_user
    db, err := GetDb("db_user")

    if err != nil {
        go app.Error(err)
        return nil, err
    }
    sql := "select uid,nick,avatar,last_token,last_id_source,bio,create_time,update_time from " + table + " where uid=? limit 1"
    rows, err := db.Query(sql, uid)
    if err != nil {
        go app.Error(err, "sql:", sql)
        rows.Close()
        return nil, err
    }
    user = &User{}
    for rows.Next() {
        err = rows.Scan(&user.Uid,&user.Nick, &user.Avatar, &user.LastToken,
            &user.LastIdSource, &user.Bio, &user.CreateTime, &user.UpdateTime)
        if err != nil {
            go app.Error("scan error:", err)
            continue
        }
        break
    }
    rows.Close()
    if user.Uid != 0 {
        nick, _ := base64.StdEncoding.DecodeString(user.Nick)
        user.Nick = string(nick)
        bio, _ := base64.StdEncoding.DecodeString(user.Bio)
        user.Bio = string(bio)
    }
    return user, nil
}

// 生成新的 token
func generateToken(uid int, lastToken string) string  {
    pool, err := GetRedisPool("user")
    if err != nil {
        go app.Error(err)
        return ""
    }
    rc := pool.Get()
    defer rc.Close()
    if lastToken != "" {
        rc.Do("del", "token_"+lastToken)
    }
    now := time.Now()

    usec := now.UnixNano() / 1000
    newToken := fmt.Sprintf("%s%d%d",
        config.ServerConf.HashSalt,
        uid, usec)
    newToken = fmt.Sprintf("%x", md5.Sum([]byte(newToken)))
    value := fmt.Sprintf("%d|%d", uid, now.Unix())
    rc.Do("set", "token_"+newToken, value)
    return newToken
}

func (u *User) Save() error  {
    table := fmt.Sprintf("t_user_info_%02d", u.Uid%100)
    // 选择 db_user
    db, err := GetDb("db_user")

    if err != nil {
        go app.Error(err)
        return err
    }
    u.Nick = base64.StdEncoding.EncodeToString([]byte(u.Nick))
    u.Bio = base64.StdEncoding.EncodeToString([]byte(u.Bio))
    sql := "update "+ table + " set `nick`=?, `avatar`=?, `bio`=?, `last_token`=?,`last_id_source`=?," +
        " `update_time`=? where uid=?"
    _, err = db.Exec(sql, u.Nick, u.Avatar, u.Bio, u.LastToken, u.LastIdSource, u.UpdateTime, u.Uid)
    if err != nil {
        go app.Error(err, "sql:", sql)
        return err
    }
    return nil
}

// 创建用户
// id 邮箱/手机号码/微信unionid/QQ unionid
// idSource 登录方式 0邮箱/1手机号码/2微信/3QQ
// password 邮箱登录时的密码
// openId 微信/QQ 的open_id
func CreateUser(id string, idSource int, password string, openId string) (user *User, err error)  {
    // 获取新的 uid
    uid, err := getNewUid()
    if err != nil {
        return nil, err
    }

    // 绑定 id
    bindId(uid, id, idSource, openId)

    // 增加 user_login
    addLogin(uid, id, idSource, password)

    // 创建新用户
    user = &User{}
    user.Uid = uid
    nick := fmt.Sprintf("%d", uid)
    user.Nick = base64.StdEncoding.EncodeToString([]byte(nick))
    user.Bio = ""
    user.CreateTime = int(time.Now().Unix())
    user.UpdateTime = user.CreateTime
    user.LastIdSource = idSource
    token := generateToken(uid, "")
    user.LastToken = token
    err = user.add()
    if err != nil {
        return nil, err
    }
    return user, nil
}

// 往数据库插入一条记录
func (u *User) add() error  {
    table := fmt.Sprintf("t_user_info_%02d", u.Uid%100)
    // 选择 db_user
    db, err := GetDb("db_user")

    if err != nil {
        go app.Error(err)
        return err
    }
    sql := "insert into "+ table + " (`uid`, `nick`, `avatar`, `bio`, `last_token`,`last_id_source`," +
        "`create_time`, `update_time`) values (?,?,?,?,?,?,?,?)"
    _, err = db.Exec(sql, u.Uid, u.Nick, u.Avatar, u.Bio, u.LastToken, u.LastIdSource,u.CreateTime, u.UpdateTime)
    if err != nil {
        go app.Error(err, "sql:", sql)
        return err
    }
    return nil
}

// 获取新的 uid
func getNewUid() (uid int, err error)  {
    pool, err := GetRedisPool("user")
    if err != nil {
        return 0, err
    }
    // redisClient
    rc := pool.Get()
    defer rc.Close()
    uid, err = redis.Int(rc.Do("incrby", "user_uid", 1))
    if err != nil {
        return 0, err
    }

    uid += BaseUid
    return uid, nil
}

// 绑定邮箱/手机号码/微信/QQ
func bindId(uid int, id string, idSource int, openId string)  {
    table := fmt.Sprintf("t_user_bind_%02d", uid%100)
    // 选择 db_user
    db, err := GetDb("db_user")

    if err != nil {
        go app.Error(err)
        return
    }
    sql := "insert into " + table + " (`uid`, `id`, `id_source`, `open_id`, `create_time`) " +
        "values (?,?,?,?,?)"
    createTime := int(time.Now().Unix())
    _, err = db.Exec(sql, uid, id, idSource, openId, createTime)
    if err != nil {
        go app.Error(err, "sql:", sql)
    }
}

// 往 user_login 插入一条记录
func addLogin(uid int, id string, idSource int, password string)  {
    hashVal := utils.StrHash(id)
    table := fmt.Sprintf("t_user_login_%02d", hashVal%100)
    newPassword := fmt.Sprintf("%s%s", config.ServerConf.HashSalt, password)
    newPassword = fmt.Sprintf("%x", md5.Sum([]byte(newPassword)))
    // 选择 db_user
    db, err := GetDb("db_user")

    if err != nil {
        go app.Error(err)
        return
    }
    sql := "insert into " + table + " (`uid`, `id`, `id_source`, `password`, `create_time`) " +
        "values (?,?,?,?,?)"
    createTime := int(time.Now().Unix())
    _, err = db.Exec(sql, uid, id, idSource, newPassword, createTime)
    if err != nil {
        go app.Error(err, "sql:", sql)
    }
}

func DelUserCache(uid int)  {
    pool, err := GetRedisPool("user")
    if err != nil {
        go app.Error(err)
        return
    }

    // redisClient
    rc := pool.Get()
    defer rc.Close()
    _, err = rc.Do("hDel", "user_info", uid)
    if err != nil {
        go app.Error(err)
    }
}
